//----------------------------------------------------------------------------------------------
//	Filename:	LineWorldShapeFactory.h
//	Author:		Sandro Spina
//	Date:		14/08/2012
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
#pragma once

#include <iostream>
#include <fstream>

#include <boost/shared_ptr.hpp>
#include "Shape/VertexFormats.h"
#include "Shape/PointCloud.h"

namespace Illumina
{
	namespace Core
	{		
		
		class LineWorldShapeForge
		{
		public:						
			LineWorldShapeForge()
			{
				IPointCloud::ResetMaterialsIds();
			}			

			static bool CreateSurface(const Vector3 &p_v0, float p_length, std::vector<CloudPoint> *p_pointList, float p_density, float p_radius)
			{				
				Vector3 s_start = Vector3(p_v0[0] - p_length/2, p_v0[1], p_v0[2]);
				Vector3 s_end = Vector3(p_v0[0] + p_length/2, p_v0[1], p_v0[2]);
				GeneratePointsOnLine(s_start, s_end, *p_pointList, p_density, p_radius, IPointCloud::GetNextMaterialId());
				return true;
			}

			static bool CreateDiagonal(const Vector3 &p_v0, float p_length, std::vector<CloudPoint> *p_pointList, float p_density, float p_radius)
			{
				Vector3 s_start = Vector3(p_v0[0] - p_length/2, p_v0[1] - p_length/2, p_v0[2]);
				Vector3 s_end = Vector3(p_v0[0] + p_length/2, p_v0[1] + p_length/2, p_v0[2]);
				GeneratePointsOnLine(s_start, s_end, *p_pointList, p_density, p_radius, IPointCloud::GetNextMaterialId());
				return true;
			}

			//p_v0 represent the base of the stairs. Creates a stairs with an object (table or chair for now) on each horizontal surface
			static bool CreateStairs(const Vector3 &p_v0, float p_stepSize, int p_numberOfSteps, std::vector<CloudPoint> *p_pointList, float p_density, float p_radius)
			{
				Vector3 s_start = Vector3(p_v0);
				Vector3 s_stop = s_start + Vector3(p_stepSize, 0.0f, 0.0f);
				//generate a random scale value between 0.5 and 3
				float scale = (1.5-0.5)*(rand() / float(RAND_MAX)) + 0.5;
				int nxtMaterialId = IPointCloud::GetNextMaterialId();
				for (int i = 0; i<p_numberOfSteps; i++)
				{					
					GeneratePointsOnLine(s_start, s_stop, *p_pointList, p_density, p_radius, nxtMaterialId);
					s_start = s_stop;
					s_stop = s_start + Vector3::UnitYPos;
					if (i!=p_numberOfSteps-1)	GeneratePointsOnLine(s_start, s_stop, *p_pointList, p_density, p_radius, nxtMaterialId);
					s_start = s_stop;
					s_stop = s_start + Vector3(p_stepSize, 0.0f, 0.0f);
					scale = (1.5-0.5)*(rand() / float(RAND_MAX)) + 0.5;
				}
				return true;
			}

			//p_v0 represent the base of the stairs. Creates a stairs with an object (table or chair for now) on each horizontal surface
			static bool CreateStairsWithObjects(const Vector3 &p_v0, float p_stepSize, int p_numberOfSteps, std::vector<CloudPoint> *p_pointList, float p_density, float p_radius)
			{
				Vector3 s_start = Vector3(p_v0);
				Vector3 s_stop = s_start + Vector3(p_stepSize, 0.0f, 0.0f);
				//generate a random scale value between 0.5 and 3
				float scale = (1.5-0.5)*(rand() / float(RAND_MAX)) + 0.5;
				int nxtMaterialId = IPointCloud::GetNextMaterialId();
				for (int i = 0; i<p_numberOfSteps; i++)
				{					
					GeneratePointsOnLine(s_start, s_stop, *p_pointList, p_density, p_radius, nxtMaterialId);
					//Place object on this stair
					CreateChair(s_start +  Vector3(p_stepSize/2, 0.0f, 0.0f), p_pointList, p_density, p_radius, scale);
					s_start = s_stop;
					s_stop = s_start + Vector3::UnitYPos;
					if (i!=p_numberOfSteps-1)	GeneratePointsOnLine(s_start, s_stop, *p_pointList, p_density, p_radius, nxtMaterialId);
					s_start = s_stop;
					s_stop = s_start + Vector3(p_stepSize, 0.0f, 0.0f);
					scale = (1.5-0.5)*(rand() / float(RAND_MAX)) + 0.5;
				}
				return true;
			}

			//p_v0 represents the position of the surface over which this chair should lie.
			static bool CreateChair(const Vector3 &p_v0, std::vector<CloudPoint> *p_pointList, float p_density, float p_radius, float p_scale)
			{
				//A chair consists of several lines forming the back, seat, front and back leg components.
				//Minimally there must be two vectors (one line) per component
				List<Vector3>	chair_desc;				
				float scale = p_scale;
				float distance_to_surface = 1000000.0f;
				float distance_to_centre = -1000000.0f;
				float smallest_x_position = 1000000.0f;
				float largest_x_position = -1000000.0f;
				
				int chair_switch = rand() % 4;
				Vector3 seat_back;

				switch(chair_switch) {
				
					case (1) :
						//m_back desribes the back component of the chair 
						chair_desc.PushBack(p_v0);
						chair_desc.PushBack(p_v0+Vector3::UnitYPos*scale);

						//m_seat describes the seat component of the chair
						seat_back = p_v0+Vector3::UnitXPos*scale; 
						chair_desc.PushBack(p_v0);				
						chair_desc.PushBack(seat_back);

						//m_front leg describes the front leg component of the chair
						//should start from somewhere near the end of the seat component above					
						chair_desc.PushBack(seat_back);				
						chair_desc.PushBack(seat_back+Vector3::UnitYNeg*scale);

						//Finally add the line represeting the back leg component
						chair_desc.PushBack(p_v0);
						chair_desc.PushBack(p_v0+Vector3::UnitYNeg*scale);
						break;

					case (2) :
						//m_back desribes the back component of the chair. 
						chair_desc.PushBack(p_v0);
						chair_desc.PushBack(p_v0+Vector3::UnitYPos*scale);

						//m_seat describes the seat component of the chair
						seat_back = p_v0-Vector3::UnitXPos*scale; 
						chair_desc.PushBack(p_v0);				
						chair_desc.PushBack(seat_back);

						//m_front leg describes the front leg component of the chair
						//should start from somewhere near the end of the seat component above					
						chair_desc.PushBack(seat_back);				
						chair_desc.PushBack(seat_back+Vector3::UnitYNeg*scale);

						//Finally add the line represeting the back leg component
						chair_desc.PushBack(p_v0);
						chair_desc.PushBack(p_v0+Vector3::UnitYNeg*scale);
						break;

					case (3) :
						//m_back desribes the back component of the chair. 
						chair_desc.PushBack(p_v0);
						chair_desc.PushBack(p_v0+Vector3::UnitYPos*scale);
						chair_desc.PushBack(p_v0+Vector3::UnitYPos*scale);
						chair_desc.PushBack(p_v0+Vector3::UnitYPos*scale+(Vector3(0.2f, 0.2f, 0.0f)*scale));


						//m_seat describes the seat component of the chair
						seat_back = p_v0-Vector3::UnitXPos*scale; 
						chair_desc.PushBack(p_v0);				
						chair_desc.PushBack(seat_back);

						//m_front leg describes the front leg component of the chair
						//should start from somewhere near the end of the seat component above					
						chair_desc.PushBack(seat_back);				
						chair_desc.PushBack(seat_back+Vector3::UnitYNeg*scale);

						//Finally add the line represeting the back leg component
						chair_desc.PushBack(p_v0);
						chair_desc.PushBack(p_v0+Vector3::UnitYNeg*scale);
						break;


					default :						
						chair_desc.PushBack(p_v0);
						chair_desc.PushBack(p_v0+Vector3::UnitYPos*scale);
						seat_back = p_v0+Vector3::UnitXPos*scale; 
						chair_desc.PushBack(p_v0);				
						chair_desc.PushBack(seat_back);
						chair_desc.PushBack(seat_back);				
						chair_desc.PushBack(seat_back+Vector3::UnitYNeg*scale);
						chair_desc.PushBack(p_v0);
						chair_desc.PushBack(p_v0+Vector3::UnitYNeg*scale);
						break;
				}

				//************
				//Determine the lowest value of Y ... and adjust if necessary all other lines descibing the chair
				//Determine the centre of the stair and align the chair with the centre.
				for (int i=0; i<chair_desc.Size(); i++) 
				{
					if (chair_desc.At(i)[1] < distance_to_surface) distance_to_surface = chair_desc.At(i)[1];
					if (chair_desc.At(i)[0] > distance_to_centre) distance_to_centre = chair_desc.At(i)[0];
					if (chair_desc.At(i)[0] > largest_x_position) largest_x_position = chair_desc.At(i)[0];
					if (chair_desc.At(i)[0] < smallest_x_position) smallest_x_position = chair_desc.At(i)[0];
				}

				float distance_to_shift_Y = p_v0[1] - distance_to_surface;
				float distance_to_shift_X = (p_v0[0] - distance_to_centre) / 2;
				float x_span = Maths::Abs(largest_x_position - smallest_x_position);
				if (Maths::Abs(distance_to_shift_X) < x_span/2) distance_to_shift_X = x_span/2;

				for (int i=0; i<chair_desc.Size(); i++)
				{
					chair_desc.At(i)[1] = chair_desc.At(i)[1] + distance_to_shift_Y;
					chair_desc.At(i)[0] = chair_desc.At(i)[0] + distance_to_shift_X;
				}

				//*************				
				//Finally iterate over componenets, generate points and add to pointList
				int nxtMaterialId = IPointCloud::GetNextMaterialId();
				for (int i=0; i<chair_desc.Size()-1; i=i+2) 
				{
					GeneratePointsOnLine(chair_desc.At(i), chair_desc.At(i+1), *p_pointList, p_density, p_radius, nxtMaterialId);
				}

				return true;
			}

			//p_v0 represents the position of the surface over which this table should lie.
			static bool CreateTable(const Vector3 &p_v0, std::vector<CloudPoint> *p_pointList, float p_density, float p_radius, float p_scale)
			{
				//A chair consists of several lines forming the back, seat, front and back leg components.
				//Minimally there must be two vectors (one line) per component
				List<Vector3>	table_desc;				

				float scale = p_scale;
				float distance_to_surface = 1000000.0f;
				
				int table_switch = rand() % 4;
				Vector3 seat_back;

				switch(table_switch) {
				
					case (1) :
						//top component, surface of the table 
						table_desc.PushBack(p_v0);
						table_desc.PushBack(p_v0+Vector3::UnitXPos*3*scale);

						//now the two legs of the table
						seat_back = p_v0+Vector3::UnitXPos*scale; 
						table_desc.PushBack(p_v0);				
						table_desc.PushBack(p_v0-Vector3::UnitYPos*scale);

						table_desc.PushBack(p_v0+Vector3::UnitXPos*3*scale);
						table_desc.PushBack((p_v0+Vector3::UnitXPos*3*scale) - Vector3::UnitYPos*scale);						
						break;

					case (2) :
						//top component, surface of the table 
						table_desc.PushBack(p_v0);
						table_desc.PushBack(p_v0+Vector3::UnitXPos*2*scale);

						//now the two legs of the table
						seat_back = p_v0+Vector3::UnitXPos*scale; 
						table_desc.PushBack(p_v0);				
						table_desc.PushBack(p_v0-Vector3::UnitYPos*scale);

						table_desc.PushBack(p_v0+Vector3::UnitXPos*2*scale);
						table_desc.PushBack((p_v0+Vector3::UnitXPos*2*scale) - Vector3::UnitYPos*scale);
						break;

					case (3) :
						//top component, surface of the table 
						table_desc.PushBack(p_v0);
						table_desc.PushBack(p_v0+Vector3::UnitXPos*1.5*scale);

						//now the two legs of the table
						seat_back = p_v0+Vector3::UnitXPos*scale; 
						table_desc.PushBack(p_v0);				
						table_desc.PushBack(p_v0-Vector3::UnitYPos*scale);

						table_desc.PushBack(p_v0+Vector3::UnitXPos*1.5*scale);
						table_desc.PushBack((p_v0+Vector3::UnitXPos*1.5*scale) - Vector3::UnitYPos*scale);
						break;


					default :						
						//top component, surface of the table 
						table_desc.PushBack(p_v0);
						table_desc.PushBack(p_v0+Vector3::UnitXPos*scale);

						//now the two legs of the table
						seat_back = p_v0+Vector3::UnitXPos*scale; 
						table_desc.PushBack(p_v0);				
						table_desc.PushBack((p_v0-Vector3(0.f, 0.5f, 0.f))*scale);

						table_desc.PushBack(p_v0+Vector3::UnitXPos*scale);
						table_desc.PushBack(((p_v0+Vector3::UnitXPos*scale) - Vector3(0.f, 0.5f, 0.f)) * scale);
						break;
				}

				//************
				//Determine the lowest value of Y ... and adjust if necessary all other lines descibing the chair
				for (int i=0; i<table_desc.Size(); i++) 
				{
					if (table_desc.At(i)[1] < distance_to_surface) distance_to_surface = table_desc.At(i)[1];
				}

				float distance_to_shift = p_v0[1] - distance_to_surface;				

				for (int i=0; i<table_desc.Size(); i++) 
				{
					table_desc.At(i)[1] = table_desc.At(i)[1] + distance_to_shift;
				}

				//*************				
				//Finally iterate over componenets, generate points and add to pointList
				int nxtMaterialId = IPointCloud::GetNextMaterialId();
				for (int i=0; i<table_desc.Size()-1; i=i+2) 
				{
					GeneratePointsOnLine(table_desc.At(i), table_desc.At(i+1), *p_pointList, p_density, p_radius, nxtMaterialId);
				}

				return true;
			}

			/*density is a value between 0 (sparsest) and 1 (densest) point generation
			*
			*
			*/ 
			static void GeneratePointsOnLine(Vector3 &p_start, Vector3 &p_end, std::vector<CloudPoint> &p_pointList, float p_density, float p_radius, int p_materialId) 
			{		
				float dist = Vector3::Distance(p_start, p_end);
				int max_points = dist / (p_radius*2);				
				int num_points = (max_points * p_density) +1; //there's always at least one point
				float dist_between_points = dist / num_points;
				Vector3 direction = Vector3::Normalize(p_end - p_start);				
				for (int i=0; i<=num_points; i++)
				{
					VertexP next_point = VertexP();
					next_point.Position = p_start + (direction*i*dist_between_points);					
					p_pointList.push_back(CloudPoint(next_point, p_materialId, 0, -1));
				}
			}

		};
	}
}